package com.zengfa.task.client.config;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import com.zengfa.task.client.annotation.TaskCallback;
import com.zengfa.task.client.annotation.TaskNode;
import com.zengfa.task.client.annotation.TaskNodeMonitor;
import com.zengfa.task.client.annotation.TaskService;
import com.zengfa.task.client.model.TaskDefine;
import com.zengfa.task.client.model.FlowNodeDefine;
import com.zengfa.task.client.model.FlowNodeMonitorDefine;
import com.zengfa.task.client.model.TaskCallbackDefine;
import com.zengfa.task.client.vo.JobContextVo;
import com.zengfa.task.client.vo.JobResult;

public class TaskNodeConfig implements ApplicationRunner {

	public static final Map<String, TaskDefine> TASKS = new HashMap<>();
	
	@Autowired
	private ApplicationContext applicationContext;
	
	@Override
	public void run(ApplicationArguments args) throws Exception {
		Map <String, Object> beanMap = this.applicationContext.getBeansWithAnnotation(TaskService.class);
		Map<String, FlowNodeDefine> flowNodeMap = new HashMap<>();
		Map<String, FlowNodeMonitorDefine> nodeMonitorMap = new HashMap<>();
		Map<String, TaskCallbackDefine> callbackMap = new HashMap<>();
		for (String beanName : beanMap.keySet()) {
			initAnnotation(beanName, beanMap.get(beanName), flowNodeMap, nodeMonitorMap, callbackMap);
		}
		initFlows(flowNodeMap, nodeMonitorMap, callbackMap);
	}
	
	private void initAnnotation(String beanName, Object service, 
			Map<String, FlowNodeDefine> nodeMap, 
			Map<String, FlowNodeMonitorDefine> nodeMonitorMap,
			Map<String, TaskCallbackDefine> callbackMap) {
		Class<?> clazz = AopProxyUtils.ultimateTargetClass(service);
		Method[] methods = clazz.getDeclaredMethods();
		if (methods == null || methods.length < 1) {
			return;
		}
		for(Method method : methods) {
			FlowNodeMonitorDefine monitor = makeNodeMonitor(service, method);
			if (monitor != null) {
				nodeMonitorMap.put(monitor.getName(), monitor);
			}
			TaskCallbackDefine callback = makeCallbackDefine(service, method);
			if (callback != null) {
				callbackMap.put(callback.getGroup(), callback);
			}
			FlowNodeDefine node = makeNode(service, method);
			if (node != null) {
				String group = node.getGroup();
				Integer order = node.getOrder();
				nodeMap.put(group + "-" + order, node);
			}
		}
	}
	
	private FlowNodeMonitorDefine makeNodeMonitor(Object service, Method method) {
		TaskNodeMonitor annotation = method.getAnnotation(TaskNodeMonitor.class);
		if (annotation == null) {
			return null;
		}
		Parameter[] parameters = method.getParameters();
		if (parameters.length != 1) {
			return null;
		}
		Parameter parameter = parameters[0];
		if(!parameter.getType().equals(JobContextVo.class)) {
			return null;
		}
		String monitorName = annotation.name();
		FlowNodeMonitorDefine monitor = new FlowNodeMonitorDefine();
		monitor.setService(service);
		monitor.setMethod(method);
		monitor.setName(monitorName);
		return monitor;
	}
	
	private TaskCallbackDefine makeCallbackDefine(Object service, Method method) {
		TaskCallback annotation = method.getAnnotation(TaskCallback.class);
		if (annotation == null) {
			return null;
		}
		Parameter[] parameters = method.getParameters();
		if (parameters.length != 2) {
			return null;
		}
		Parameter parameter = parameters[0];
		if(!parameter.getType().equals(JobContextVo.class)) {
			return null;
		}
		parameter = parameters[1];
		if(!parameter.getType().equals(JobResult.class)) {
			return null;
		}
		String group = annotation.group();
		TaskDefine taskDefine = TASKS.get(group);
		if (taskDefine == null) {
			taskDefine = new TaskDefine();
			taskDefine.setTaskName(group);
			TASKS.put(group, taskDefine);
		}
		TaskCallbackDefine callback = new TaskCallbackDefine();
		callback.setService(service);
		callback.setMethod(method);
		callback.setGroup(group);
		return callback;
	}
	
	private FlowNodeDefine makeNode(Object service, Method method) {
		TaskNode annotation = method.getAnnotation(TaskNode.class);
		if (annotation == null) {
			return null;
		}
		Parameter[] parameters = method.getParameters();
		if (parameters.length != 1) {
			return null;
		}
		Parameter parameter = parameters[0];
		if(!parameter.getType().equals(JobContextVo.class)) {
			return null;
		}
		String group = annotation.group();
		TaskDefine taskDefine = TASKS.get(group);
		if (taskDefine == null) {
			taskDefine = new TaskDefine();
			taskDefine.setTaskName(group);
			TASKS.put(group, taskDefine);
		}
		BitSet bs = taskDefine.getBs();
		if (bs == null) {
			bs = new BitSet();
			taskDefine.setBs(bs);
		}
		Integer order = annotation.order();
		String code = annotation.code();
		String desc = annotation.desc();
		if (order < 0) {
			order = 0;
		}
		bs.set(order);
		if (!StringUtils.hasText(code)) {
			code = method.getName();
		}
		FlowNodeDefine node = new FlowNodeDefine(code, service, method, desc);
		node.setGroup(group);
		node.setOrder(order);
		node.setRunType(annotation.runType());
		node.setMonitorName(annotation.monitorName());
		return node;
	}
	
	private void initFlows(Map<String, FlowNodeDefine> flowNodeMap, Map<String, FlowNodeMonitorDefine> nodeMonitorMap, Map<String, TaskCallbackDefine> callbackMap) {
		Set<String> groups = TASKS.keySet();
		for (String group : groups) {
			TaskDefine taskDefine = TASKS.get(group);
			TaskCallbackDefine callback = callbackMap.get(group);
			if (callback != null) {
				taskDefine.setTaskCallbackDefine(callback);
			}
			List<FlowNodeDefine> nodeList = taskDefine.getDefineFlowNodes();
			if (nodeList == null) {
				nodeList = new ArrayList<>();
				taskDefine.setDefineFlowNodes(nodeList);
			}
			BitSet bs = taskDefine.getBs();
			if (bs == null || bs.length() < 1) {
				continue;
			}
			List<FlowNodeDefine> _nodeList = nodeList;
			bs.stream().forEach(index -> {
				FlowNodeDefine node = flowNodeMap.get(group + "-" + index);
				_nodeList.add(node);
				String monitorName = node.getMonitorName();
				if(StringUtils.hasText(monitorName)) {
					FlowNodeMonitorDefine monitor = nodeMonitorMap.get(monitorName);
					node.setFlowNodeMonitorDefine(monitor);
				}
			});
		}
	}
	
}
